package com.boot2.core.conf;

import lombok.extern.apachecommons.CommonsLog;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import java.util.Properties;

/**
 * @author zhangweilin
 * @date 2018/11/24 21:05
 * @description: 全局声明式事务(此配置解决事务内修改不可见问题, 而另一个却事务内修改不可见, 此坑消耗了我整整两天一夜研究琢磨, )
 * 在一个系统只有一个数据源情况下，任何一个工程中不需要再有额外的配置，只需要此配置即可，但是有多数据源时，可基于此创建多数据源的事务
 */
//@Component
@Configuration
@EnableJpaAuditing
@CommonsLog
public class TxConfig {
    private static final Properties transactionProperties = new Properties();

    static {
        /**
         * 所有的事务拦截器都走统一的事务规则
         */
        transactionProperties.setProperty("init*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("add*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("insert*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("create*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("persist*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("update*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("modify*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("merge*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("bind*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("del*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("drop*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("remove*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("reset*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("cancel*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("login*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("save*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("update*", "PROPAGATION_REQUIRED,-Exception");
        transactionProperties.setProperty("delete*", "PROPAGATION_REQUIRED,-Exception");

        transactionProperties.setProperty("get", "PROPAGATION_REQUIRED,-Exception,readOnly");
        transactionProperties.setProperty("select", "PROPAGATION_REQUIRED,-Exception,readOnly");
        transactionProperties.setProperty("find", "PROPAGATION_REQUIRED,-Exception,readOnly");
        transactionProperties.setProperty("query", "PROPAGATION_REQUIRED,-Exception,readOnly");
        transactionProperties.setProperty("load", "PROPAGATION_REQUIRED,-Exception,readOnly");
        transactionProperties.setProperty("list", "PROPAGATION_REQUIRED,-Exception,readOnly");
        transactionProperties.setProperty("show", "PROPAGATION_REQUIRED,-Exception,readOnly");
        transactionProperties.setProperty("fech", "PROPAGATION_REQUIRED,-Exception,readOnly");
    }

    @Autowired
    PlatformTransactionManager transactionManager;

    /**
     * 由spring自启动时创建的默认事务拦截器
     *
     * @param transactionManager
     * @return
     * @throws Exception
     */
    @Bean(name = "txAdvice")
    public TransactionInterceptor createTransactionInterceptor() {
        TransactionInterceptor tsi = new TransactionInterceptor(transactionManager, transactionProperties);
        return tsi;
    }

    /**
     * 拦截器名字不能提前加，需要先有bean才能加,要先有bean，,而在后期动态添加的拦截器名字无效。
     * 直接加尚未定义的事务拦截器名称,不管对应的拦截器是用代码创建还是用@Bean创建，全部事务都无效，目前没找到原因
     * 所以非主数据源也用自动事务的尝试失败了，目前只能做到：多数据源时，主事务是自动的，非主事务，需要手动在方法头上加@Transactional("transactionManagerTest3")
     *
     * 此特性:使用DatasourceInterceptor做数据源切换aop,使用BeanNameAutoProxyCreator做事务aop, 但是此情况，当service有父类时，无法获取到类上的注解，故放弃
     * @return
     */
//    @Bean
//    public BeanNameAutoProxyCreator txProxy() {
//        Set<String> datasourceSet = new HashSet<>();
////        //当配置了多数据源时，需要把切换拦截器也注入到上下文中
//        String dynamicDatasourceInterceptor = "dynamicDatasourceInterceptor";
//        boolean containsBean = SpringUtil.getApplicationContext().containsBean(dynamicDatasourceInterceptor);
//        if (containsBean) {
//            datasourceSet.add(dynamicDatasourceInterceptor);
//        }
////        datasourceSet.add("dynamicDatasourceAop");
//        datasourceSet.add("txAdvice");
//        BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
//        creator.setInterceptorNames(datasourceSet.toArray(new String[]{}));
//        creator.setBeanNames("*Service", "*Dao");
//        creator.setProxyTargetClass(true);
////        creator.setOrder(Integer.MAX_VALUE);
//        return creator;
//    }


     /**
      * 此据说是采用aspect的方式拦截事务，好让aspect数据源切换的aop，能在同一条order线上，以设置order来控制数据源切换和事务的顺序，但是实际此AnnotationAwareAspectJAutoProxyCreator未生效，事务不起作用
      * /
//    @Bean
//    public AnnotationAwareAspectJAutoProxyCreator txProxy()
//    {
//        /*
//         * 必须使用AspectJ方式的AutoProxy，这样才能和Aspect保持统一的aop拦截方式，否则不同的拦截方式会导致order失效
//         */
//        List<String> txExecutionList = new ArrayList<>();
//        txExecutionList.add("execution (public com..*.service..*.*(..))");
////        txExecutionList.add("execution (public com..*.service..*.*(..)) || execution (public com..*.dao..*.*(..))");
////        txExecutionList.add("!execution (public com..*.redis..*.*(..))");
//
//        AnnotationAwareAspectJAutoProxyCreator c = new AnnotationAwareAspectJAutoProxyCreator();
//        c.setInterceptorNames("txAdvice");
//        c.setIncludePatterns(txExecutionList);
//        c.setProxyTargetClass(true);
//        c.setOrder(30);
//        return c;
//    }


    /**
     * 此为通过ajpect的方式走事务，且能生效
     * @return
     */
    @Bean
    public Advisor txAdviceAdvisor(){
        /* 声明切点的面
         * 切面（Aspect）：切面就是通知和切入点的结合。通知和切入点共同定义了关于切面的全部内容——它的功能、在何时和何地完成其功能。
         * */
        AspectJExpressionPointcut pointcut=new AspectJExpressionPointcut();
        /*声明和设置需要拦截的方法,用切点语言描写*/
//        pointcut.setExpression("execution (* com..*.service..*(..))");
        pointcut.setExpression("(execution(* com..*.service..*(..)) || execution(* com..*.dao..*(..))) && !execution(* com..*.redis..*(..))");
        /*设置切面=切点pointcut+通知TxAdvice*/
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor(pointcut, createTransactionInterceptor());
        defaultPointcutAdvisor.setOrder(30);//此要保证在DynamicDatasourceAop中的之后
        return defaultPointcutAdvisor;
    }
}